// 2025/9/9: 首个版本
// 2025/10/13: Get/Cancel/Remove方法支持客户端调用,改为异步方法

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using ASEva;
using SO = System.Reflection.ObfuscationAttribute;

namespace RemoteConsole
{
    class ContentTransfer
    {
        public static String TransferFile(String contentID, String filePath)
        {
            if (AgencyLocal.ClientSide) return null;
            if (String.IsNullOrEmpty(filePath)) return null;
            if (!File.Exists(filePath)) return null;

            var filePathBytes = Encoding.UTF8.GetBytes(filePath);
            var proto = new ContentHeaderProto
            {
                CommitTime = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                GUID = Guid.NewGuid().ToString().Replace("-", ""),
                Type = ContentTransferType.File,
                ContentID = contentID ?? "",
                TargetSize = filePathBytes.Length,
            };
            var protoBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(proto));

            var packet = new byte[4 + protoBytes.Length + filePathBytes.Length];
            BitConverter.TryWriteBytes(packet.AsSpan(0, 4), protoBytes.Length);
            Array.Copy(protoBytes, 0, packet, 4, protoBytes.Length);
            Array.Copy(filePathBytes, 0, packet, 4 + protoBytes.Length, filePathBytes.Length);

            AgencyLocal.PublishData("Console.UploadContent", packet);
            return proto.GUID;
        }

        public static String TransferFolder(String contentID, String folderPath)
        {
            if (AgencyLocal.ClientSide) return null;
            if (String.IsNullOrEmpty(folderPath)) return null;
            if (!Directory.Exists(folderPath)) return null;

            var folderPathBytes = Encoding.UTF8.GetBytes(folderPath);
            var proto = new ContentHeaderProto
            {
                CommitTime = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                GUID = Guid.NewGuid().ToString().Replace("-", ""),
                Type = ContentTransferType.Folder,
                ContentID = contentID ?? "",
                TargetSize = folderPathBytes.Length,
            };
            var protoBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(proto));

            var packet = new byte[4 + protoBytes.Length + folderPathBytes.Length];
            BitConverter.TryWriteBytes(packet.AsSpan(0, 4), protoBytes.Length);
            Array.Copy(protoBytes, 0, packet, 4, protoBytes.Length);
            Array.Copy(folderPathBytes, 0, packet, 4 + protoBytes.Length, folderPathBytes.Length);

            AgencyLocal.PublishData("Console.UploadContent", packet);
            return proto.GUID;
        }

        public static String TransferBinary(String contentID, byte[] binary)
        {
            if (AgencyLocal.ClientSide) return null;
            if (binary == null || binary.Length == 0) return null;

            var proto = new ContentHeaderProto
            {
                CommitTime = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                GUID = Guid.NewGuid().ToString().Replace("-", ""),
                Type = ContentTransferType.Binary,
                ContentID = contentID ?? "",
                TargetSize = binary.Length,
            };
            var protoBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(proto));

            var packet = new byte[4 + protoBytes.Length + binary.Length];
            BitConverter.TryWriteBytes(packet.AsSpan(0, 4), protoBytes.Length);
            Array.Copy(protoBytes, 0, packet, 4, protoBytes.Length);
            Array.Copy(binary, 0, packet, 4 + protoBytes.Length, binary.Length);

            AgencyLocal.PublishData("Console.UploadContent", packet);
            return proto.GUID;
        }

        public static String TransferText(String contentID, String text)
        {
            if (AgencyLocal.ClientSide) return null;
            if (String.IsNullOrEmpty(text)) return null;

            var binary = Encoding.UTF8.GetBytes(text);

            var proto = new ContentHeaderProto
            {
                CommitTime = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                GUID = Guid.NewGuid().ToString().Replace("-", ""),
                Type = ContentTransferType.Text,
                ContentID = contentID ?? "",
                TargetSize = binary.Length,
            };
            var protoBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(proto));

            var packet = new byte[4 + protoBytes.Length + binary.Length];
            BitConverter.TryWriteBytes(packet.AsSpan(0, 4), protoBytes.Length);
            Array.Copy(protoBytes, 0, packet, 4, protoBytes.Length);
            Array.Copy(binary, 0, packet, 4 + protoBytes.Length, binary.Length);

            AgencyLocal.PublishData("Console.UploadContent", packet);
            return proto.GUID;
        }

        public static DataSubscriber CreateContentSubscriber(String contentID)
        {
            if (AgencyLocal.ClientSide) return null;
            return AgencyLocal.SubscribeData("Console.DownloadedContent." + contentID, 100, 60);
        }

        public static ReceivedContent[] ReceiveContents(DataSubscriber subscriber)
        {
            if (AgencyLocal.ClientSide) return [];

            var dequeued = subscriber.Dequeue();
            if (dequeued == null || dequeued.Length == 0) return [];

            var output = new List<ReceivedContent>();
            foreach (var packet in dequeued)
            {
                if (packet.Length < 4) continue;

                var protoByteSize = BitConverter.ToInt32(packet.AsSpan(0, 4));
                if (packet.Length < 4 + protoByteSize) continue;

                var proto = JsonSerializer.Deserialize<ContentHeaderProto>(packet.AsSpan(4, protoByteSize));
                if (proto == null) continue;
                if (packet.Length != 4 + protoByteSize + proto.TargetSize) continue;

                switch (proto.Type)
                {
                    case ContentTransferType.File:
                        {
                            var filePath = Encoding.UTF8.GetString(packet.AsSpan(4 + protoByteSize, proto.TargetSize));
                            output.Add(new ReceivedFileContent { CommitTime = proto.CommitTime, GUID = proto.GUID, ContentID = proto.ContentID, FilePath = filePath });
                        }
                        break;
                    case ContentTransferType.Folder:
                        {
                            var folderPath = Encoding.UTF8.GetString(packet.AsSpan(4 + protoByteSize, proto.TargetSize));
                            output.Add(new ReceivedFolderContent { CommitTime = proto.CommitTime, GUID = proto.GUID, ContentID = proto.ContentID, FolderPath = folderPath });
                        }
                        break;
                    case ContentTransferType.Binary:
                        {
                            var binary = packet.AsSpan(4 + protoByteSize, proto.TargetSize).ToArray();
                            output.Add(new ReceivedBinaryContent { CommitTime = proto.CommitTime, GUID = proto.GUID, ContentID = proto.ContentID, Binary = binary });
                        }
                        break;
                    case ContentTransferType.Text:
                        {
                            var text = Encoding.UTF8.GetString(packet.AsSpan(4 + protoByteSize, proto.TargetSize));
                            output.Add(new ReceivedTextContent { CommitTime = proto.CommitTime, GUID = proto.GUID, ContentID = proto.ContentID, Text = text });
                        }
                        break;
                }
            }
            return output.ToArray();
        }

        public static async Task<ContentTransferState> GetContentTransferState()
        {
            var state = new ContentTransferState();
            var jsonString = await AgencyAsync.GetGlobalVariable("Console.ContentDownloadTasks", null);
            if (!String.IsNullOrEmpty(jsonString)) state.DownloadTasks = JsonSerializer.Deserialize<ContentTransferTask[]>(jsonString);
            jsonString = await AgencyAsync.GetGlobalVariable("Console.ContentUploadTasks", null);
            if (!String.IsNullOrEmpty(jsonString)) state.UploadTasks = JsonSerializer.Deserialize<ContentTransferTask[]>(jsonString);
            return state;
        }

        public static async Task CancelUploadTask(String guid)
        {
            if (String.IsNullOrEmpty(guid)) return;
            await AgencyAsync.PublishData("Console.CancelContentUploadTask", Encoding.UTF8.GetBytes(guid));
        }

        public static async Task CancelDownloadTask(String guid)
        {
            if (!String.IsNullOrEmpty(guid)) return;
            await AgencyAsync.PublishData("Console.CancelContentDownloadTask", Encoding.UTF8.GetBytes(guid));
        }

        public static async Task<String[]> GetIdlePorts()
        {
            var idlePortRows = await AgencyAsync.GetGlobalVariable("Console.IdlePorts", null);
            if (String.IsNullOrEmpty(idlePortRows)) return [];
            else return idlePortRows.Split('\n');
        }

        public static async Task RemoveIdlePort(String port)
        {
            if (String.IsNullOrEmpty(port)) return;
            await AgencyAsync.PublishData("Console.RemoveIdlePort", Encoding.UTF8.GetBytes(port));
        }
    }

    [SO]
    enum ContentTransferType
    {
        File = 1,
        Folder = 2,
        Binary = 3,
        Text = 4,
    }

    [SO]
    class ContentHeaderProto
    {
        public String CommitTime { get; set; }
        public String GUID { get; set; }
        public ContentTransferType Type { get; set; }
        public String ContentID { get; set; }
        public int TargetSize { get; set; }
    }

    class ReceivedContent
    {
        public String CommitTime { get; set; }
        public String GUID { get; set; }
        public String ContentID { get; set; }
    }

    class ReceivedFileContent : ReceivedContent
    {
        public String FilePath { get; set; }
    }

    class ReceivedFolderContent : ReceivedContent
    {
        public String FolderPath { get; set; }
    }

    class ReceivedBinaryContent : ReceivedContent
    {
        public byte[] Binary { get; set; }
    }

    class ReceivedTextContent : ReceivedContent
    {
        public String Text { get; set; }
    }

    [SO]
    class ContentTransferProgressDetails
    {
        [JsonPropertyName("S")]
        public double Slice { get; set; }
        [JsonPropertyName("T")]
        public double Transfer { get; set; }
        [JsonPropertyName("R")]
        public double Restore { get; set; }
    }

    [SO]
    class ContentTransferTask
    {
        [JsonPropertyName("T")]
        public String CommitTime { get; set; }
        [JsonPropertyName("G")]
        public String GUID { get; set; }
        [JsonPropertyName("I")]
        public String ContentID { get; set; }
        [JsonPropertyName("B")]
        public String Brief { get; set; }
        [JsonPropertyName("C")]
        public ulong PayloadByteCount { get; set; } // only valid after sliced
        [JsonPropertyName("P")]
        public ContentTransferProgressDetails ProgressDetails { get; set; }

        public double TotalProgress
        {
            get
            {
                if (ProgressDetails == null) return 0;
                else return ProgressDetails.Slice * 0.25 + ProgressDetails.Transfer * 0.5 + ProgressDetails.Restore * 0.25;
            }
        }
    }

    class ContentTransferState
    {
        public ContentTransferTask[] DownloadTasks { get; set; } = [];
        public ContentTransferTask[] UploadTasks { get; set; } = [];
    }
}